package net.wicp.tams.commons.binlog.impl;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;

import lombok.extern.slf4j.Slf4j;
import net.wicp.tams.commons.Result;
import net.wicp.tams.commons.apiext.ByteUtil;
import net.wicp.tams.commons.binlog.IBinlogRead;
import net.wicp.tams.commons.binlog.bean.EventBean;
import net.wicp.tams.commons.binlog.bean.EventHeader;
import net.wicp.tams.commons.binlog.bean.PacketBean;
import net.wicp.tams.commons.binlog.constant.BinLogVersion;
import net.wicp.tams.commons.binlog.constant.EventFlag;
import net.wicp.tams.commons.binlog.constant.EventType;
import net.wicp.tams.commons.binlog.event.AbsEvent;
import net.wicp.tams.commons.binlog.event.RotateEvent;
import net.wicp.tams.commons.binlog.replication.down.EOFPacket;
import net.wicp.tams.commons.binlog.replication.down.ErrorPacket;
import net.wicp.tams.commons.binlog.replication.down.OKPacket;

@Slf4j
public class BinglogReadDump extends BufferedInputStream implements IBinlogRead {
	private EventBean firstEvent;
	private String fileName;
	private boolean curFile = false;// 是否只处理连接时指定的文件，默认为否，true：是，适用于补日志

	public BinglogReadDump(InputStream in) {
		super(in);
	}

	/***
	 * 不用检查魔数，因为dump不支持小于4的pos
	 */
	@Override
	public Result checkHead() {
		try {
			PacketBean bin = PacketBean.buildEventBean(in, fileName);
			firstEvent = bin.getEventBean();
			firstEvent.getEventRead().parseBody();
			BinLogVersion binLogVersion = getVersion(firstEvent.getHead());
			if (binLogVersion == null || binLogVersion != BinLogVersion.v4) {
				return Result.getError(String.format("只支持v4的binlog，不支持的binlog版本:[%s]",
						binLogVersion == null ? "" : binLogVersion.name()));
			}
			return Result.getSuc();
		} catch (Exception e) {
			return Result.getError(String.format("读binlog文件:[%s]错误", fileName));
		}
	}

	@Override
	public EventBean readEvent() throws IOException {
		if (this.available() <= 0) {
			return null;
		}
		long timestamp = ByteUtil.readLongL(readBytes(4));
		EventType eventType = EventType.get(readByte());
		long serverId = ByteUtil.readLongL(readBytes(4));
		long eventSize = ByteUtil.readLongL(readBytes(4));
		long nextEventPos = ByteUtil.readLongL(readBytes(4));
		EventFlag eventFlag = EventFlag.get(ByteUtil.readIntL(readBytes(2)));
		byte[] body = readBytes((int) eventSize - 19);
		EventHeader head = EventHeader.builder().timestamp(timestamp).eventType(eventType).serverId(serverId)
				.eventSize(eventSize).nextEventPos(nextEventPos).eventFlag(eventFlag).build();
		EventBean retobj = new EventBean(head, body);

		log.info(
				"---------------------------事件[{}]开始位置：{}，数据开始位置{}----------------------------------------------------",
				eventType == null ? "" : eventType.name(), AbsEvent.row(retobj.getBeginHead()),
				AbsEvent.row(retobj.getBeginBody()));
		return retobj;
	}

	@Override
	public void read(long pos) throws IOException {
		if (AbsEvent.hosts.get() == null || !EventType.isInit(AbsEvent.hosts.get())) {
			Result checkRet = checkHead();
			if (!checkRet.isSuc()) {
				throw new RuntimeException(checkRet.getMessage());
			}
			if (pos > firstEvent.getHead().getNextEventPos()) {
				// 读完format后要跳到要解析的流
				// this.skip(pos -
				// firstEvent.getHead().getNextEventPos());//TODO
				// 如何跳？？？？
				// 由于含有包的信息，不能直接skip
				while (true) {
					PacketBean bin = PacketBean.buildEventBean(in, fileName);
					if (bin.getEventBean().getBeginHead() >= pos) {
						bin.getEventBean().getEventRead().parseBody();// 这个事件也是解析
						break;
					}
				}
			}
		}

		while (true) {
			PacketBean bin = PacketBean.buildEventBean(in, fileName);
			if (bin.getEventBean().getHead().getEventType() == EventType.STOP_EVENT && curFile) {
				bin.getEventBean().getEventRead();
				break;
			}
			if (bin.getEventBean().getHead().getEventType() == EventType.ROTATE_EVENT) {// 跳转事件
				RotateEvent rotateEvent = (RotateEvent) bin.getEventBean().getEventRead();
				rotateEvent.parseBody();
				fileName = rotateEvent.getNextBinlogName();
				log.info("跳转到下一个文件:【{}】，位置:【{}】", fileName, rotateEvent.getNextPosition());
				continue;
			}

			// if (bin.getEventBean().getHead().getEventType() ==
			// EventType.FORMAT_DESCRIPTION_EVENT) {// 最后一个事件
			// break;
			// }
			byte packetMarker = bin.getPacketMarker();
			if (packetMarker == ErrorPacket.PACKET_MARKER) {
				final ErrorPacket binPacket = new ErrorPacket(bin);
				log.error("读事件包出错：[{}]", binPacket.getBody().toString(true));
			} else if (packetMarker == EOFPacket.PACKET_MARKER) {
				final EOFPacket binPacket = new EOFPacket(bin);
				log.error("读事件包结束：[{}]", binPacket.getBody().toString(true));
			} else if (packetMarker == OKPacket.PACKET_MARKER) {
				if (bin.getEventBean() == null) {
					break;
				}
				Result ret = bin.getEventBean().getEventRead().parseBody();
				if (ret != null) {
					log.info(ret.getMessage());
				}
			} else {
				log.info("不认识的包");
			}

		}

	}

	// 需要在第9个位开始读,读13位
	public BinLogVersion getVersion(EventHeader firstHead) {
		if (firstHead == null) {
			return null;
		}
		BinLogVersion retobj = null;
		switch (firstHead.getEventType()) {
		case FORMAT_DESCRIPTION_EVENT:
			retobj = BinLogVersion.v4;
			break;
		case START_EVENT_V3:
			long eventSize = firstHead.getEventSize();
			if (eventSize == (13 + 56)) {
				return BinLogVersion.v1;
			} else if (eventSize == (19 + 56)) {
				return BinLogVersion.v3;
			}
			break;
		default:
			break;
		}
		return retobj;
	}

	//////////// ////////////////////////////////////////下面是一些扩展方法////////////////////////////////////////////////////////////////////////////
	/**
	 * 得到当前位置
	 * 
	 * @return
	 */
	public long getCurPos() {
		return this.pos;
	}

	public byte[] readBytes(int length) throws IOException {
		final byte[] r = new byte[length];
		this.read(r, 0, length);
		return r;
	}

	public byte readByte() throws IOException {
		return readBytes(1)[0];
	}

	public String getFileName() {
		return fileName;
	}

	public void setFileName(String fileName) {
		this.fileName = fileName;
	}

	public boolean isCurFile() {
		return curFile;
	}

	public void setCurFile(boolean curFile) {
		this.curFile = curFile;
	}

}
